RuoYi-Vue
- RuoYi-Vue版本,采用了前后端分离的单体架构设计
https://gitee.com/ys-gitee/RuoYi-Vue3
https://gitee.com/y_project/RuoYi-Vue
课程版本 |
---|
JDK 11 |
MySQL 8 |
Redis 5 |
Maven 3.6 |
Node 16 (Vue3) |
- 技术选型:SpringBoot、SpringSecurity、MyBatis、Jwt、VUE3、Element-Plus
从VCS导入项目:
点击
VCS
菜单。选择
Get from Version Control...
选项。URL:https://gitee.com/y_project/RuoYi-Vue.git Directory:C:\Users\Pluminary\Desktop\RuoYi-Vue
输入项目地址:
- 在弹出的窗口中,您可以看到不同的版本控制系统(例如Git, SVN等)。
- 选择您要导入的项目所使用的版本控制系统。
- 在接下来的窗口中,您需要输入项目的URL地址。这通常是项目的仓库地址,例如Git仓库的HTTPS或SSH链接。
① 若模块没有导入进去没有亮 就Maven → clean → package
C:\Users\Pluminary\Desktop\RuoYi-Vue\sql 导入Sql文件两个
sql/quartz.sql
sql/ry_20240629.sql
先gitwzs28150/RuoYi-Vue3: :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统下载这里面的资料,在里面C:\Users\Pluminary\Desktop\itcast>code ./RuoYi-Vue3
导入vscode里面,再安装依赖(要进入Vue3里面才能利用package.json去生成)npm install
→ 运行前端项目npm run dev
// npm install慢的话 就用中国镜像去下载
npm install --registr=https://registry.npmmirror.com
C:\Users\Pluminary\Desktop\itcast\RuoYi-Vue3>npm run dev
> ruoyi@3.8.8 dev
> vite
VITE v5.3.2 ready in 2026 ms
➜ Local: http://localhost:8080/
➜ Network: http://10.254.3.124:8080/
➜ Network: http://192.168.104.38:8080/
➜ Network: http://192.168.22.1:8080/
➜ Network: http://192.168.36.1:8080/
➜ press h + enter to show help
再导入课程管理.sql
数据库 → 在若依的后台系统 → 系统工具 → 代码生成 → 配置好后下载代码 → 导入数据库
→ 导入RuoYi生成的前端代码C:\Users\Pluminary\Downloads\ruoyi\vue\api
的course导入vscode中 C:\Users\Pluminary\Desktop\itcast\RuoYi-Vue3\src\api\course
&& C:\Users\Pluminary\Downloads\ruoyi\vue\views
的course导入vscode中 C:\Users\Pluminary\Desktop\itcast\RuoYi-Vue3\src\api\course
→ 导入RuoYi生成的后端代码C:\Users\Pluminary\Desktop\itcast\RuoYi-Vue3\src\views\course
的com 并且导入配置文件 && C:\Users\Pluminary\Downloads\ruoyi\main\resources
的mapper导入到idea的resource中
功能详解
权限控制
- 若依内置了强大的权限控制系统,为企业级项目提供了通用的解决方案
- **demo账号 (超级管理员)**,查看所有功能菜单
- **zhangsan账号 (市场专员)**,查看线索菜单
- **yueyue账号 (销售专员)**,查看商机、合同等菜单
- RBAC (基于角色的控制访问) 是一种广泛使用的访问控制模型,通过角色来分配和管理用户的菜单权限
表名 | 说明 |
---|---|
sys_dept | 部门表 |
sys_post | 岗(职)位信息表 |
sys_menu | 菜单权限表 |
sys_role | 角色信息表 |
sys_role_dept | 角色和部门关联表 |
sys_role_menu | 角色和菜单关联表 |
sys_user | 用户信息表 |
sys_user_post | 用户与岗位关联表 |
sys_user_role | 用户和角色关联表 |
创建新用户小智并关联课研人员角色,仅限课程管理和统计分析菜单访问。
① 创建菜单
② 创建角色,并分配权限课研人员
③ 创建用户,并关联角色xiaozhi
若依通过简单的功能配置实现RBC的权限管理
数据字典
- 若依内置的数据字典,用于维护系统中常见的静态数据。例如:性别、状态
- 功能包括:字典类型管理、字典数据管理
- 表关系说明【一对多】
表名 | 说明 |
---|---|
sys_dict_type | 字典类型表 |
sys_dict_data | 字典数据表 |
dict_id | dict_name | dict_type |
---|---|---|
1 | 用户性别 | sys_user_sex |
2 | 菜单状态 | sys_show_hide |
dict_code | dict_sort | dict_label | dict_value | dict_type |
---|---|---|---|---|
1 | 1 | 男 | 0 | sys_user_sex |
2 | 2 | 女 | 1 | sys_user_sex |
3 | 3 | 未知 | 2 | sys_user_sex |
将一些不经常修改的数据(课程管理的学科字段)改为数据字典维护,以免占用大量空间
添加字典类型和数据
系统管理 → 字典管理 → 新增 → 添加字典类型
字典名称:学科
字典类型:course_subject
第二页点进去添加字典数据
javaEE → 0 → 1修改代码生成信息
系统工具 → 代码生成 → 编辑课程管理 → subject课程学科 显示类型从文本框改成下拉框 → 字典类型 是学科 → 下载最新的代码
只需要修改前端vue组件 因为只改了从前端文本框到下拉框 和一些数据字典下载代码,导入前端
C:\Users\Pluminary\Downloads\ruoyi (1)\vue\views\course\course\index.vue去替换前端的index.vue 此时去若依前端查看课程管理→课程学科就会发现已经添加新的进去了
此时去
课程管理
里面找课程学科:JavaEE
进行筛选不会出 因为若依底层
http://10.254.2.179/dev-api/course/course/list?pageNum=1&pageSize=10&subject=0
要从数据库里把JavaEE的subject改成0
优点:降低数据库的存储压力 提高磁盘利用率
其他功能
参数设置:对系统中的参数进行动态维护
系统管理 → 参数设置 → 验证码开关 → 修改 → 参数键值 → false
还可以开启是否 用户注册功能 → 前端代码隐藏需要修改 → src/views/login.vue → 97行注册开关
const register = ref(true) 此时登录界面就有立即注册 跳转注册通知公告(半成品):促进组织内部信息传递
系统管理 → 通知公告 → 新增
日志管理:轻松追踪用户行为和系统运行状况
系统管理 → 通知公告 → 日志管理 → 操作日志
系统监控
若依提供了一些列强大的监控工具,能够帮助开发者和运维快速了解应用程序的性能状态
系统监控 → 在线用户 && 缓存列表
数据监控【Druid Monitor】 → ruoyi && 123456
定时任务
若依为定时任务功能提供方便友好的web界面,实现动态管理任务
@Component public class MyTask{ @Scheduled(cron = "0/5 *****?") public void showTime(){ sout("定时任务开始执行:" + new Date()); } } // 硬编码 改代码需要重新修改 重新编译 重新上传...
每间隔5秒,控制台输出系统时间
创建任务类
创建一个类
C:\Users\Pluminary\Desktop\RuoYi-Vue\ruoyi-quartz\src\main\java\com\ruoyi\quartz\task\MyTask.java
package com.ruoyi.quartz.task; import org.springframework.stereotype.Component; import java.util.Date; @Component public class MyTask { public void showTime(){ System.out.println("定时任务开始执行:" + new Date()); } }
添加任务规则
系统监控 → 定时任务 → 新增 →
任务名称:输出时间 任务分组:默认
调用方式:myTask.showTime()
Cron表达式生成器:从0秒开始,每5秒执行一次 → 0/5 * * * * ?
开启输出时间 状态打开!然后每间隔5秒就会向控制台输出时间
定时任务开始执行:Wed Nov 06 18:12:30 CST 2024
18:12:30.001 [quartzScheduler_Worker-4] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - > Preparing: insert into sys_job_log( job_name, job_group, invoke_target, job_message, status, create_time )values( ?, ?, ?, ?, ?, sysdate() ) 18:12:30.002 [quartzScheduler_Worker-4] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - –> Parameters: 输出时间(String), DEFAULT(String), myTask.showTime()(String), 输出时间 总共耗时:0毫秒(String), 0(String) 18:12:30.009 [quartzScheduler_Worker-4] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - < Updates: 1 定时任务开始执行:Wed Nov 06 18:12:35 CST 2024 18:12:35.006 [quartzScheduler_Worker-5] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - > Preparing: insert into sys_job_log( job_name, job_group, invoke_target, job_message, status, create_time )values( ?, ?, ?, ?, ?, sysdate() ) 18:12:35.006 [quartzScheduler_Worker-5] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - –> Parameters: 输出时间(String), DEFAULT(String), myTask.showTime()(String), 输出时间 总共耗时:0毫秒(String), 0(String) 18:12:35.012 [quartzScheduler_Worker-5] DEBUG c.r.q.m.S.insertJobLog - [debug,135] - < Updates: 1启动任务
官方有提供可以训练的模型
在这里面:com/ruoyi/quartz/task/RyTask.java
表单构建
通过表单构建工具,单独制作一个添加课程的表单页面
制作表单并导出
系统工具 → 表单构建 → 左侧行容器 → 拖入第一个单行文本 → 右侧可以改名字 如果想实现一行两个文本的化 就把
表单栅格调小一点
→ 选择性组件的下拉选择托到右面 → 修改字段名、标题、表单栅格…
日期范围 → 课程有效期 → 选择型组件里的日期范围 命名为:课程有效期
文件上传组件 → 课程封面
评分 → 推荐指数
多行文本 → 课程介绍复制到前端工程
搞完后打开前端工程把add.vue它放在 src/views/course/course/中
创建动态菜单
系统管理 → 菜单管理 → 添加菜单 →
菜单类型:菜单
菜单名称:添加课程
显示排序:1
路由地址:course/add
组件路径:course/course/add
刷新界面 就会有菜单管理→添加课程了
系统工具
代码生成
- 代码生成器,根据数据库表结构自动生成前后端CRUD代码
- 提供三种生成模板:单表、树表、主子表(一对多)
- 树表是一种展示层级数据的表格,能展开折叠,清晰呈现父子关系,便于管理
系统工具 → 代码生成 → 导入部门表 → 编辑 → 生成信息 → 生成模板:树表 →
树编码字段:dept_id:部门id
树父编码字段:parent_id:父部门id
树名称字段:dept_name:部门名称
提交后下载代码
导入后就是 系统管理/部门管理 的树型结构界面了
dept_id | parent_id | ancestors | dept_name |
---|---|---|---|
100 | 0 | 0 | 若依科技 |
101 | 100 | 0,100 | 深圳总公司 |
102 | 100 | 0,100 | 长沙分公司 |
108 | 102 | 0,100,101 | 市场部门 |
109 | 102 | 0,100,102 | 财务部门 |
系统接口
Swagger,能够自动生成API的同步在线文档,并提供Web界面进行接口调用和测试
系统工具 → 系统接口
若依的Token在应用程序里 需要搞token进去
测试:获取用户列表GET
得到Token(F12后找应用程序 → Cookie若依→ 找到Admin-Token一定是当前ip地址的Token→ 在Authorize中设置Token令牌 → 去后端改swagger的请求前缀pathMapping:/因为他的地址前面默认佩戴/dev-api# Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /
然后重启后台项目刷新浏览器打开接口再调用Token再去测试
此时就操作成功了{ "code": 200, "msg": "操作成功", "data": [ { "userId": 1, "username": "admin", "password": "admin123", "mobile": "15888888888" }, { "userId": 2, "username": "ry", "password": "admin123", "mobile": "15666666666" } ] }
若依常用功能?
① 权限控制
② 数据字典
③ 定时任务
④ 表单构建
⑤ 代码生成
项目结构

表结构

前端代码分析
api/course/course.js
用于向后端发送Ajax请求的接口代码views/course/course/index.vue
用于在浏览器展示课程的视图组件
src\views\course\course\index.vue
<template>
<div class="app-container">
<!-- :model做双向绑定 将前端录入条件封装给响应对象 v-show控制搜索栏显示隐藏-->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="课程编码" prop="code">
<!-- v-model双向绑定code(前端课程编码) clearable清理用户输入信息 @keyup键盘回车事件完成搜索-->
<el-input
v-model="queryParams.code"
placeholder="请输入课程编码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="课程学科" prop="subject">
<!-- v-for遍历课程学科的字典数据列表 :lable展示label :value提交value值-->
<el-select v-model="queryParams.subject" placeholder="请选择课程学科" clearable>
<el-option
v-for="dict in course_subject"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="课程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入课程名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="适用人群" prop="applicablePerson">
<el-select v-model="queryParams.applicablePerson" placeholder="请选择适用人群" clearable>
<el-option
v-for="dict in course_applicable_person"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<!-- @click点击新增按钮弹出新增 v-hasPermi自定义属性完成菜单显示/隐藏 -->
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['course:course:add']"
>新增</el-button>
</el-col>
<!-- :disabled表示这个框是否可用 -->
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['course:course:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['course:course:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['course:course:export']"
>导出</el-button>
</el-col>
<!-- 点击会控制'搜索栏'显示隐藏 @queryTable重新加载表格展示数据 -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- v-loading展示表格的加载状态 遍历展示courseList 事件监听器监听选中行 -->
<el-table v-loading="loading" :data="courseList" @selection-change="handleSelectionChange">
<!-- 当用户勾选复选框 触发@selection-change -->
<el-table-column type="selection" width="55" align="center" />
<!-- 展示具体数据源数据 -->
<el-table-column label="课程id" align="center" prop="id" />
<el-table-column label="课程编码" align="center" prop="code" />
<el-table-column label="课程学科" align="center" prop="subject">
<!-- 通过scope拿到整个表格数据 通过:value="scope.row.subject"拿到字典值去匹配字典数据列表 找到该值对应的标签显示给前端-->
<template #default="scope">
<dict-tag :options="course_subject" :value="scope.row.subject"/>
</template>
</el-table-column>
<el-table-column label="课程名称" align="center" prop="name" />
<el-table-column label="价格" align="center" prop="price" />
<el-table-column label="适用人群" align="center" prop="applicablePerson">
<template #default="scope">
<dict-tag :options="course_applicable_person" :value="scope.row.applicablePerson"/>
</template>
</el-table-column>
<el-table-column label="课程介绍" align="center" prop="info" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<!-- 使用了模板插槽 -->
<template #default="scope">
<!-- @click="handleUpdate(scope.row)把当前行的数据传给当前方法 v-hasPermi自定义权限属性-->
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['course:course:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['course:course:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<!-- v-show如果大于0条就显示反之隐藏 :total展示总条数 展示分页页码 @pagination="getList"换页后新数据的查询-->
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改课程管理对话框 -->
<!-- el-dialog默认隐藏的 点击会显示 append-to-body默认将对话框在body上追加显示 :title动态绑定标题(新增和修改不一样)-->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<!-- :model来双向绑定 :rules校验规则-->
<el-form ref="courseRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="课程编码" prop="code">
<el-input v-model="form.code" placeholder="请输入课程编码" />
</el-form-item>
<el-form-item label="课程学科" prop="subject">
<el-select v-model="form.subject" placeholder="请选择课程学科">
<el-option
v-for="dict in course_subject"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="课程名称" prop="name">
<el-input v-model="form.name" placeholder="请输入课程名称" />
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="form.price" placeholder="请输入价格" />
</el-form-item>
<el-form-item label="适用人群" prop="applicablePerson">
<el-select v-model="form.applicablePerson" placeholder="请选择适用人群">
<el-option
v-for="dict in course_applicable_person"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="课程介绍" prop="info">
<el-input v-model="form.info" placeholder="请输入课程介绍" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<!-- 提交前先进行表单规则的校验:rules="rules" -->
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Course">
// 引入后端api接口文件
import { listCourse, getCourse, delCourse, addCourse, updateCourse } from "@/api/course/course";
// 获取当前实例代理对象,用于访问组件数据、方法
const { proxy } = getCurrentInstance();
// 获取课程学科的数据字典
const { course_applicable_person, course_subject } = proxy.useDict('course_applicable_person', 'course_subject');
// 列表数据
const courseList = ref([]);
// 是否显示弹框
const open = ref(false);
// 是否显示加载状态
const loading = ref(true);
// 是否显示搜索栏
const showSearch = ref(true);
// 复选框,被选中id的数组
const ids = ref([]);
// 复选框,是否单选,用于高亮修改、删除按钮
const single = ref(true);
// 复选框,是否多选,仅高亮删除按钮
const multiple = ref(true);
// 总(记录)条数
const total = ref(0);
// 用于区分新增、修改对话框标题
const title = ref("");
// 定义reactive响应式对象
const data = reactive({
// 新增或修改表单数据
form: {},
// 搜索条件参数
queryParams: {
pageNum: 1,
pageSize: 10,
code: null,
subject: null,
name: null,
applicablePerson: null,
},
// 表单校验规则
rules: {
code: [
{ required: true, message: "课程编码不能为空", trigger: "blur" }
],
subject: [
{ required: true, message: "课程学科不能为空", trigger: "change" }
],
name: [
{ required: true, message: "课程名称不能为空", trigger: "blur" }
],
price: [
{ required: true, message: "价格不能为空", trigger: "blur" }
],
applicablePerson: [
{ required: true, message: "适用人群不能为空", trigger: "change" }
],
info: [
{ required: true, message: "课程介绍不能为空", trigger: "blur" }
],
}
});
// 将data对象的三个属性,转换为ref响应式对象
const { queryParams, form, rules } = toRefs(data);
/** 查询课程管理列表 */
function getList() {
loading.value = true;
listCourse(queryParams.value).then(response => {
courseList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
function reset() {
form.value = {
id: null,
code: null,
subject: null,
name: null,
price: null,
applicablePerson: null,
info: null,
createTime: null,
updateTime: null
};
proxy.resetForm("courseRef");
}
/** 搜索按钮操作 */
function handleQuery() {
// 最新的从第一页开始 再发送请求
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
// 把选中的复选框对象传递过来
function handleSelectionChange(selection) {
// 拿到对象调用map方法进行遍历取每个复选框的id
// 封装给ids的响应式数组对象
ids.value = selection.map(item => item.id);
// 控制修改和删除按钮是否高亮可用的 23默认为true
single.value = selection.length != 1;
// 修改按钮只要大于0 就是false 那么修改按钮可用使用
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加课程管理";
}
/** 修改按钮操作 */
// 拿到行对象 重置 取出当前行id或一个id
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getCourse(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改课程管理";
});
}
/** 提交按钮 */
function submitForm() {
// '修改课程'对表单进行校验 正则规则...是否必填
proxy.$refs["courseRef"].validate(valid => {
if (valid) {// 区分新增还是修改的操作
if (form.value.id != null) {
updateCourse(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addCourse(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
// 一行或数组
const _ids = row.id || ids.value;
// 防止误操作
proxy.$modal.confirm('是否确认删除课程管理编号为"' + _ids + '"的数据项?').then(function() {
return delCourse(_ids);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('course/course/export', {
...queryParams.value
}, `course_${new Date().getTime()}.xlsx`)
}
getList();
</script>
src\api\course\course.js
// 封装了请求和响应拦截器 下面return每个都调用请求
import request from '@/utils/request'
// 查询课程管理列表
// 接收用户输入参数 调用工具类把参数传过去 向后端发送请求完成课程列表的查询
// 然后返回前端并展示数据
export function listCourse(query) {
return request({
url: '/course/course/list',
method: 'get',
params: query
})
}
// 查询课程管理详细
// 点击修改按钮时候根据id去查询 返回给前端
export function getCourse(id) {
return request({
url: '/course/course/' + id,
method: 'get'
})
}
// 新增课程管理
// 当点击确定按钮的时候 就把数据添加进来发送请求后返回前端
export function addCourse(data) {
return request({
url: '/course/course',
method: 'post',
data: data
})
}
// 修改课程管理
// 修改完毕(根据id去查找数据库的)
export function updateCourse(data) {
return request({
url: '/course/course',
method: 'put',
data: data
})
}
// 删除课程管理
// 批量/单体删除
export function delCourse(id) {
return request({
url: '/course/course/' + id,
method: 'delete'
})
}
再次熟悉:前+后端结构
若i18n乱码的情况下
file -> settings -> editor -> file ecoding -> default encoding for properties files:utf-8
后端代码分析
CourseController
ICourseService及实现类
CourseMapper及映射方法
Course
BaseController:web层通用数据处理

/**
* 查询课程管理列表
*/
@PreAuthorize("@ss.hasPermi('course:course:list')")
@GetMapping("/list")
public TableDataInfo list(Course course) {
//1.开启分页
startPage();
//2.查询课程列表
List<Course> list = courseService.selectCourseList(course);
return getDataTable(list);
}
// 在分页查询那块会附带着两个封装好的sql语句
// select * from tb_course where xxx 【逐步细分成两个小sql】
1. select count(*) from tb_course where xxx //总记录数
2. select * from tb_course where xxx limit ?,? //获取当前的数据列表
分页原理
AjaxResult:操作消息提醒
权限解读
@PreAuthorize
注解是Spring Security框架中用来做权限检查的。- 它在运行方法前先验证权限,权限够就放行,不够就拦截
@RestController
@RequestMapping("/course/course")
public class CourseController extends BaseController {
@Autowired
private ICourseService courseService;
/**
* 查询课程管理列表
*/
// 问题:我怎么知道该用户有没有权限呢?基于RBC权限模型
@PreAuthorize("@ss.hasPermi('course:course:list')")
@GetMapping("/list")
public TableDataInfo list(Course course) {
startPage();
List<Course> list = courseService.selectCourseList(course);
return getDataTable(list);
}
}
菜单名称 | 排序 | 权限标识 |
---|---|---|
课程管理 | 1 | course:course:list |
课程管理查询 | 1 | course:course:query |
课程管理新增 | 2 | course:course:add |
课程管理修改 | 3 | course:course:edit |
课程管理删除 | 4 | course:course:remove |
课程管理导出 | 5 | course:course:export |
PermissionService.java
源码解读在后期
/**
* RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母
*
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permission);
return hasPermissions(loginUser.getPermissions(), permission);
}
二次开发 —— 苍穹外卖
若依框架修改器
- 若依框架修改器是一个可以一键修改RuoYi框架包名、项目名等的工具
E:\Java实例项目1-20套\2024-Java若依框架专题课\01-基础篇\资料\04-二次开发\若依框架修改器.exe
选择系列:RuoYi-Vue
目录名称: sky
项目名:sky
包名:com.sky
artifactId:sky
groupId:com.sky
站点名称:外卖管理系统
C:\Users\Pluminary\Desktop\20241108150415\sky
新建业务模块
新建
sky-merchant
子模块商家管理新建子模块
sky-merchant
→ Advanced Settings → GroupId:com.sky父工程版本锁定
pom.xml(sky总) <!-- 商家管理--> <dependency> <groupId>com.sky</groupId> <artifactId>sky-merchant</artifactId> <version>${sky.version}</version> </dependency>
pom.xml(sky-merchant) <dependencies> <dependency> <groupId>com.sky</groupId> <artifactId>sky-framework</artifactId> </dependency> </dependencies>
sky-admin添加依赖
pom.xml(sky-admin) <dependency> <groupId>com.sky</groupId> <artifactId>sky-merchant</artifactId> </dependency>
菜品管理
- 利用若依代码生成器(主子表模板),生成菜品管理的前后端代码
tb_dish【菜品管理】 |
---|
id |
name |
price |
image |
description |
status |
create_time |
update_time |
tb_dish_flavor【菜品口味关系表】 |
---|
id |
dish_id |
name |
value |
准备SQL并导入数据库
E:\Java实例项目1-20套\2024-Java若依框架专题课\01-基础篇\资料\04-二次开发\菜品管理
配置代码生成信息【主子表的生成】
在若依代码生成 → 导入表tb_dish 和 tb_dish_flavor
下载代码并导入项目
修改代码 → 系统工具 → 代码生成 → 菜品管理(修改) →
基本信息:实体类名称→Dish 作者→itheima
字段信息
参考页面原型生成→ 系统管理 → 字典管理 →字典名称:售卖状态
字典类型:dish_status
第二页点进去售卖状态的字典类型 dish_status → 新增数据标签:停售 + 起售
数据键值:0 + 1
显示排序:1 + 2
回到系统工具 → 代码生成 → 菜品管理修改 → 售卖状态 → 显示类型:下拉框 → 字典类型:售卖状态
→ 代码生成 → 修改菜单配置信息 → 菜品
→ 上面的生成信息
生成模板:主子表
生成模块名:merchant
生成业务名:dish
关联子表的表名:tb_dish_flavor:菜品口味关系表
子表关联的外键名:dish_id:菜品
→ 在代码生成 → tb_dish_flavor菜品口味关系表 → 点击编辑
基本信息:
实体名:DishFalvor
→ 生成代码(main后端 + vue前端 + dishMenu.sql数据库动态菜单) → 动态sql导入进去 → 前端vue将merchant导入到./src/api中,将views导入到views中 → 在前端中将java和resources导入到merchant模块中
对菜品管理进行升级改造
主键隐藏掉 售价前缀¥ 修改时间时分秒
src\views\merchant\dish\index.vue
把 <el-table-column label="主键" align="center" prop="id" /> 打注释符
////////////////////////////////////////////////////////////////////////////
插入¥流程:
<el-table-column label="售价" align="center" prop="price" /> 修改为 →
<el-table-column label="售价" align="center" prop="price">
<template #default="scope">
<div>
¥{{ scope.row.price }}
</div>
</template>
</el-table-column>
通义灵码操作:
先解析那行代码,然后输入使用vue3语法在售价前显示¥
////////////////////////////////////////////////////////////////////////////
生成年月日时分秒:
<el-table-column label="更新时间" align="center" prop="updateTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
修改为:
修改图片回显bug
新增毛血旺的图片若依默认保存在了本地服务器而不是阿里云上
/profile/upload/2024/11/10/毛血旺_20241110093243A001.jpg
C:\Users\Pluminary\Desktop\20241108150415\sky\sky-admin\src\main\resources\application.yml
# 项目相关配置 ruoyi: # 名称 name: RuoYi # 版本 version: 3.8.8 # 版权年份 copyrightYear: 2024 # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: D:/ruoyi/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数字计算 char 字符验证 captchaType: math
D:\ruoyi\uploadPath\upload\2024\11\10
文件上传组件标签修改 增加&& item.indexOf("http") === -1
watch(() => props.modelValue, val => { if (val) { // 首先将值转为数组 const list = Array.isArray(val) ? val : props.modelValue.split(","); // 然后将数组转为对象数组 fileList.value = list.map(item => { if (typeof item === "string") { if (item.indexOf(baseUrl) === -1 && item.indexOf("http") === -1) { item = { name: baseUrl + item, url: baseUrl + item }; } else { item = { name: item, url: item }; } } return item; }); } else { fileList.value = []; return []; } },{ deep: true, immediate: true });
修改口味列表数组格式 改为下拉框的口味搭配
src\views\merchant\dish\index.vue
//------------------------------------------------
// 定义口味名称和口味列表静态数据
const dishFlavorListSelect = ref([
{name:"辣度", value:["不辣","微辣","中辣","重辣"]},
{name:"忌口", value:["不要葱","不要蒜","不要香菜","不要辣"]},
{name:"甜味", value:["无糖","少糖","半糖"]}
]);
//------------------------------------------------
src\views\merchant\dish\index.vue
<template #default="scope">
<!-- <el-input v-model="scope.row.name" placeholder="请输入口味名称" /> -->
<!-- label是最终看到下拉框的内容 value是用户提交的内容 -->
<el-select v-model="scope.row.name" placeholder="请选择口味名称">
<el-option
v-for="dishFlavor in dishFlavorListSelect"
:key="dishFlavor.name"
:label="dishFlavor.name"
:value="dishFlavor.name"
>
</el-option>
</el-select>
</template>
</el-table-column>
src\views\merchant\dish\index.vue【修改当选中辣度时候 后面的规格】
// 存储当前选中口味列表数组
const checkValueList = ref([]);
// 定义改变口味名称时更新当前选中的口味列表
function changeFlavorName(row){
// 清空当前行的value
row.value = [];
// 根据选中的name去查找静态数据的value
checkValueList.value = dishFlavorListSelect.value.find(item=>item.name==row.name).value;
}
//增加了一个 @change 注意:如果是多选框一定要 → multiple
<el-table-column label="口味名称" prop="name" width="150">
<template #default="scope">
<!-- <el-input v-model="scope.row.name" placeholder="请输入口味名称" /> -->
<!-- label是最终看到下拉框的内容 value是用户提交的内容 -->
<el-select v-model="scope.row.name" placeholder="请选择口味名称"
@change="changeFlavorName(scope.row)">
<el-option
v-for="dishFlavor in dishFlavorListSelect"
:key="dishFlavor.name"
:label="dishFlavor.name"
:value="dishFlavor.name"
>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="口味列表" prop="value" width="150">
<template #default="scope">
<!-- <el-input v-model="scope.row.value" placeholder="请输入口味列表" /> -->
<el-select v-model="scope.row.value" placeholder="请选择口味列表" multiple>
<el-option
v-for="value in checkValueList"
:key="value"
:label="value"
:value="value"
/>
</el-select>
</template>
</el-table-column>
//------------------------------------------------
// 定义口味名称和口味列表静态数据
const dishFlavorListSelect = ref([
{name:"辣度", value:["不辣","微辣","中辣","重辣"]},
{name:"忌口", value:["不要葱","不要蒜","不要香菜","不要辣"]},
{name:"甜味", value:["无糖","少糖","半糖"]}
]);
// 存储当前选中口味列表数组
const checkValueList = ref([]);
// 定义改变口味名称时更新当前选中的口味列表
function changeFlavorName(row){
// 根据选中的name去查找静态数据的value
checkValueList.value = dishFlavorListSelect.value.find(item=>item.name==row.name).value;
}
//------------------------------------------------
// 此时报错了
11:59:00.441 [http-nio-8080-exec-64] ERROR c.s.f.w.e.GlobalExceptionHandler - [handleRuntimeException,100] - 请求地址'/merchant/dish',发生未知异常.
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 239] (through reference chain: com.sky.system.domain.Dish["dishFlavorList"]->java.util.ArrayList[0]->com.sky.system.domain.DishFlavor["value"])
/*
根据错误日志,问题出在请求地址 /merchant/dish 处理过程中,JSON 解析时出现了类型不匹配的问题。具体来说,Dish 对象中的 dishFlavorList 字段下的 DishFlavor 对象的 value 字段期望接收一个 String 类型的值,但实际接收到的是一个数组。
*/
解决序列化问题bug将口味列表中value通过JSON工具类转换为字符串
提交数据后是字符串 而不是数组
在331行
/** 提交按钮 */
function submitForm() {
proxy.$refs["dishRef"].validate(valid => {
if (valid) {
form.value.dishFlavorList = dishFlavorList.value;
// 将口味列表中value通过JSON工具类转换为字符串
form.value.dishFlavorList.forEach(item=>item.value = JSON.stringify(item.value))
if (form.value.id != null) {
updateDish(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addDish(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
解决后端添加成功后 修改后口味列表无法回显再增加个非空判断
因为前端提交了字符串給后端,后端再回去修改的时候无法解析字符串 拿到字符串后返回JSON数组即可
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getDish(_id).then(response => {
form.value = response.data;
dishFlavorList.value = response.data.dishFlavorList;
// 将口味列表的value字符串转成json数组
if(dishFlavorList.value!=null){
form.value.dishFlavorList.forEach(item=>item.value = JSON.parse(item.value))
}
open.value = true;
title.value = "修改菜品管理";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["dishRef"].validate(valid => {
if (valid) {
form.value.dishFlavorList = dishFlavorList.value;
// 将口味列表中value通过JSON工具类转换为字符串
if(form.value.dishFlavorList!=null){
form.value.dishFlavorList.forEach(item=>item.value = JSON.stringify(item.value))
}
if (form.value.id != null) {
updateDish(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addDish(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
解决修改时无法修改口味列表的下拉框
給口味下拉框绑定一个获取焦点事件 再该事件内调用方法根据当前行的口味名称去查询静态数据 再赋值給静态数组
// 删除清除策略
// 定义口味列表获取焦点时更新当前选中的口味列表
function FocusFlavorName(row){
// 根据选中的name去查找静态数据的value
checkValueList.value = dishFlavorListSelect.value.find(item=>item.name==row.name).value;
}
// 加个 @focus
<el-table-column label="口味列表" prop="value" width="150">
<template #default="scope">
<!-- <el-input v-model="scope.row.value" placeholder="请输入口味列表" /> -->
<el-select v-model="scope.row.value" placeholder="请选择口味列表" multiple
@focus = "FocusFlavorName(scope.row)">
<el-option
v-for="value in checkValueList"
:key="value"
:label="value"
:value="value"
/>
</el-select>
</template>
</el-table-column>
二次开发——页面调整
将原有的页面,调整为外卖管理系统的项目标识
- 浏览器标签页icon、标题
- 系统页面中的logo、标题
- 去除源码 & 文档
- 主题和自定义图标
- 登录页面中标题、背景图
icon:public中的favicon.ico 改为 favicon.ico.bak 就作废了
把新的图标复制进来改成favicon.ico标题:最外层index.html
< title> 外卖管理系统 < /title>
但是改完没有效果 是因为运行环境給覆盖了.env.development
修改一下logo放在了静态资源页面
src\assets\logo\logo.png修改顶部源码和文档图标内容
src\layout\components\Navbar.vue<!-- <el-tooltip content="源码地址" effect="dark" placement="bottom"> <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" /> </el-tooltip> <el-tooltip content="文档地址" effect="dark" placement="bottom"> <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" /> </el-tooltip> -->
iconfont-阿里巴巴矢量图标库
下载一个菜品管理的小图标
然后给它复制进去src\assets\icons\svg\菜品管理.svg
随后回到菜单管理修改图标就好登录界面图片修改:
src\views\login.vue 修改一下标题
背景图 → src\assets\images\login-background1.jpg
通过css样式修改背景图
171行
background-image: url("../assets/images/login-background.jpg");
修改为background-image: url("../assets/images/login-background1.jpg");
.login { display: flex; justify-content: right; align-items: center; height: 100%; background-image: url("../assets/images/login-background1.jpg"); background-size: cover; }
給外卖商家的员工创建角色和用户分配菜单的权限 登录后只能看到自己的功能
系统管理 → 角色管理 → 新增 →
角色名称:商家员工
权限字符串:merchant
角色顺序:4
菜单权限:父子联动
√ 菜品管理再到用户管理 → 新增 → 添加用户 →
用户昵称:波妞
用户名称:boniu
用户密码:admin123
角色:商家员工
帝可得
帝可得是一个基于物联网概念下的智能售货机运营管理系统
物联网(IOT)
让各种物品通过互联网链接起来,实现信息的交换和通信
- 智能家居
- 共享充电桩
- 智能售货机
智能售货机
是物联网技术的一个典型应用
- 物联网技术
- 智能分析与推荐
- 人员设备绑定管理
- 线上线下融合
售货机术语
区域管理
为了更高效地进行经营管理,公司将运营范围划分为若干个逻辑区域
点位选择
点位指的是智能售货机的具体放置位置
售货机功能
自动小店,摆满了各种零食
货道设计
售货机里的货道
角色与功能
- 一个完整的售货机系统由**五端五角色**组成:
- 管理员:对基础数据(区域、点位、设备、货道、商品等)进行管理
- 运维人员:投放设备、撤除设备、维修设备。
- 运营人员:补货。
- 合作商:仅提供点位,坐收渔翁之利。
- 消费者:在小程序或屏幕端下单购买商品。
帝可得项目点击链接立即查看 https://codesign.qq.com/s/426304924036117
库表设计
- 系统后台基础数据表关系说明:
AIGC
AI (Artificial Intelligence):即人工智能,是指通过计算机系统模拟人类思维和行为一种技术
它通过机器学习、深度学习等算法,使计算机具备对数据分析、理解、推理和决策的能力
AIGC (AI Generated content):是AI领域的一个应用分支,专注于利用AI技术自动生成内容
国内常见的通用大模型(AGI)产品:
- 文心一言
- 讯飞星火
- 通义千问
- KIMI
Prompt的组成
- 角色:给 AI 定义一个最匹配任务的角色,比如:「你是一位软件工程师」「你是一位小学老师」
- 指示:对任务进行描述
- 上下文:给出与任务相关的其它背景信息(尤其在多轮交互中)
- 例子:必要时给出举例,[实践证明其对输出正确性有帮助]
- 输入:任务的输入信息;在提示词中明确的标识出输入
- 输出:输出的格式描述,以便后继模块自动解析模型的输出结果,比如(JSON、Java)
先定义角色,其实就是在开头把问题域收窄,减少二义性。
案例:
角色:你是一位专业的博客作者。
指示:撰写一篇关于最新AI技术发展的文章。
上下文:文章应该涵盖AI技术的当前状态和未来趋势。
例子:可以引用最近的AI技术突破和行业专家的见解。
输入:当前AI技术的相关信息和数据。
输出:一篇结构清晰、观点鲜明的文章草稿。
角色:你是一位资深的Java开发工程师。
指示:编写一个Java函数,该函数接收两个整数参数,并返回它们的和。
上下文:这个函数将被用于一个简单的数学应用程序,该程序帮助学生练习基本的算术运算。
例子:如果你调用函数 `addNumbers(3, 5)`,它应该返回 `8`。
输入:两个整数参数,分别为 `int a` 和 `int b`。
输出:返回这两个整数的和,类型为 `int`。
常见的编程相关的Prompt
表结构
你是一个软件工程师,帮我生成MySQL的表结构
需求如下:
1,课程管理表,表名tb_course,字段有主键id、课程编码、课程学科、课程名称、课程价格、适用人群、课程介绍
其他要求:
1,每个表中都有创建时间(create_time)、修改时间(date_time)、创建人(create_by)、修改人(update_by)、备注(remark)这些字段
2,每个表的主键都是自增的
3,课程价格是整型、课程编码是字符串
4,请为每个字段都添加上comment
5,帮我给生成的表中插入一些IT课程示例数据
课程学科:Java、人工智能、大数据
适用人群:小白学员、中级程序员
生成数据库说明文档
你是一个软件工程师,现在要根据数据库的sql脚本,编写数据库说明文档,sql脚本如下:
CREATE TABLE `tb_course` (
`id` INT AUTO_INCREMENT COMMENT '主键ID',
`course_code` VARCHAR(255) NOT NULL COMMENT '课程编码',
`course_subject` VARCHAR(100) NOT NULL COMMENT '课程学科',
`course_name` VARCHAR(255) NOT NULL COMMENT '课程名称',
`course_price` INT COMMENT '课程价格',
`target_audience` VARCHAR(100) COMMENT '适用人群',
`course_introduction` TEXT COMMENT '课程介绍',
`create_time` DATETIME COMMENT '创建时间',
`update_time` DATETIME COMMENT '修改时间',
`create_by` VARCHAR(64) COMMENT '创建人',
`update_by` VARCHAR(64) COMMENT '修改人',
`remark` VARCHAR(255) COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程管理表';
输出要求是:
1,每个表以及每个表的字段都要详细说明,包括,字段名称、类型、作用
2,使用markdown的输出格式,字段的描述需要使用表格展示
3,如果表之间有关系,需要描述清楚表之间的关系
生成代码
代码生成算是比较常规的方案,用的也比较多,分为了几种情况
- 给出表生成代码(项目中常见)
- 给出表结构的ddl,可以输出这个表的增删改查的所有代码
- 给出表结构的dll,可以输出增删改查的接口文档
- 补全代码
- 例1-给出实体类,帮助编写getter、setter、toString、构造方法等等
- 例2-给出一个controller,帮助编写swagger注解等
- 提取结构(无含金量,费时间的编程)
- 例1-根据接口文档提取dto类或者vo类
生成代码流程图
有一些比较复杂的业务流程,往往需要画出流程图,现在就可以使用ai协助我们画流程图
你是一个软件工程师,为了方便理解代码执行流程,需要给出代码执行的流程图,代码如下:
// 创建工单
@Transactional
@Override
public int insertTaskDto(TaskDto taskDto) {
//1. 查询售货机是否存在
VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(taskDto.getInnerCode());
if (vm == null) {
throw new ServiceException("设备不存在");
}
//2. 校验售货机状态与工单类型是否相符
checkCreateTask(vm.getVmStatus(), taskDto.getProductTypeId());
//3. 校验这台设备是否有未完成的同类型工单,如果存在则不能创建
hasTask(taskDto.getInnerCode(), taskDto.getProductTypeId());
//4. 校验员工是否存在
Emp emp = empService.selectEmpById(taskDto.getUserId());
if (emp == null) {
throw new ServiceException("员工不存在");
}
// 5. 校验非同区域下的工作人员不能接受工单
if (emp.getRegionId() != vm.getRegionId()) {
throw new ServiceException("非同区域下的工作人员不能接受工单");
}
//6. 保存工单信息
Task task = new Task();
BeanUtil.copyProperties(taskDto, task);// 属性赋值
task.setCreateTime(DateUtils.getNowDate());// 创建时间
task.setTaskCode(generateTaskCode());// 工单编号 202405150001
task.setTaskStatus(DkdContants.TASK_STATUS_CREATE);// 创建工单
task.setAddr(vm.getAddr());
task.setRegionId(vm.getRegionId());
task.setUserName(emp.getUserName());
int taskResult = taskMapper.insertTask(task);
//7. 如果是补货工单,向工单明细表插入记录
if (task.getProductTypeId() == DkdContants.TASK_TYPE_SUPPLY) {
if (CollUtil.isEmpty(taskDto.getDetails())) {
throw new ServiceException("补货工单明细不能为空");
}
List<TaskDetails> taskDetailsList = taskDto.getDetails().stream().map(details -> {
TaskDetails taskDetails = BeanUtil.copyProperties(details, TaskDetails.class);
taskDetails.setTaskId(task.getTaskId());
return taskDetails;
}).collect(Collectors.toList());
taskDetailsService.insertBatch(taskDetailsList);
}
return taskResult;
}
输出:请使用mermaid语言来描述这个方法的流程
graph TD;
A[查询售货机] --> B(售货机不存在?)
B -- 是 --> C[抛出异常: 设备不存在]
B -- 否 --> D[校验售货机状态与工单类型]
D --> E(校验失败?)
E -- 是 --> F[抛出异常]
E -- 否 --> G[检查未完成同类型工单]
G --> H(存在未完成同类型工单?)
H -- 是 --> I[抛出异常]
H -- 否 --> J[查询员工]
J --> K(员工不存在?)
K -- 是 --> L[抛出异常: 员工不存在]
K -- 否 --> M[校验员工区域与售货机区域是否一致]
M --> N(不一致?)
N -- 是 --> O[抛出异常: 非同区域工作人员不能接受工单]
N -- 否 --> P[保存工单信息]
P --> Q(保存成功?)
Q -- 否 --> R[处理保存失败]
Q -- 是 --> S(工单类型为补货?)
S -- 是 --> T[处理补货工单明细]
T --> U[补货明细处理结果?]
U -- 失败 --> V[抛出异常: 补货工单明细不能为空]
U -- 成功 --> END(结束)
S -- 否 --> END
项目搭建(后端)
复制gitee克隆的地址
https://gitee.com/Pluminary/dkd-parent.git
,在新的idea中找到Get from Version Control
在URL中导入xxx.git
若左列模块没有高亮 右侧找maven→clean→package
MySQL的配置和导入 → sql里的sql文件导入到Database
C:\Users\Pluminary\Desktop\dkd-parent\dkd-admin\src\main\resources\application-druid.yml
连接好数据库配置信息
Redis的配置(搞个密码)
为了方便学习我没有搞redis的password 项目里的是:root【application.yml的redis里面】
打开redis服务后就可以启动啦dkd-parent\dkd-admin\src\main\java\com\dkd\DkdApplication.java
项目搭建(前端)
通过vscode克隆源码,仓库地址:https://gitee.com/ys-gitee/dkd-vue.git\
- 打开VS Code,并确保已经安装了Git。如果未安装Git,请先下载并安装。
- 在VS Code左侧的活动栏中点击”Source Control”图标,或者按下Ctrl+Shift+G,打开Git集成面板。
- 在Git集成面板上方的输入框中,选择并输入要克隆的Git仓库地址。可以是HTTP或SSH地址。
- 点击Enter键,VS Code将连接到Git仓库并拉取最新的代码
在vscode中右上角第二个小框框 点击打开命令行 导入jar包
npm install
然后npm run dev
打开项目
帝可得管理系统 http://10.254.1.228/index在VSCode项目中运行npm install命令主要是用于安装项目所需的Node.js包依赖。以下是具体的作用和步骤: 安装依赖包:当你创建一个Node.js项目或者从版本控制系统中克隆一个项目到本地时,项目中通常会包含一个package.json文件。这个文件里列出了项目所有依赖的包及其版本号。运行npm install命令会读取这个文件,然后从npm(Node Package Manager)仓库下载并安装所有列出的依赖包到项目的node_modules目录。 确保环境一致性:通过npm install,可以确保在不同的开发环境和生产环境中,项目使用的是相同版本的依赖包,这有助于避免因为环境差异导致的bug。 项目初始化:如果你是第一次在一个项目中运行npm install,它还会运行每个依赖包的install脚本,这些脚本可能会进行一些设置工作,比如编译代码、生成必要的文件等。 脚本命令:在package.json中,除了依赖项,还可以定义一些脚本命令(scripts)。npm install会使得这些命令变为可用状态,你可以在项目目录下通过npm run <script-name>来执行这些脚本。
[npm 加速,命令行修改国内镜像源【附带国内最新几个镜像】超简约版
_npm 修改国内镜像-CSDN博客](https://blog.csdn.net/m0_52172586/article/details/142930356#::text=1.查看目前的镜像源 >npm get registry 2.设置镜像源 >npm,config set registry https%3A %2F%2Fregistry.npmmirror.com 3.验证)1.查看目前的镜像源 > npm get registry 2.设置镜像源 > npm config set registry https://registry.npmmirror.com 3.验证 > npm get registry ///////////////////////////////////////// 后端maven镜像就先设置好maven地址(非C盘) 然后再去配置maven里的文件设置镜像
点位管理
需求说明
业务场景: 假设我们的公司现在有一个宏伟的计划——在北京发展业务。首先,我们需要确定几个有潜力的区域,这些区域可能是人流量大、消费能力高的商业区或居民区。然后,我们要与这些区域内的潜在合作商进行洽谈,比如商场、写字楼、学校等地方的管理者或所有者。
一旦我们与合作商达成协议,确定了合作的细节和点位,我们就可以安排工作人员去投放智能售货机了。这些点位将成为我们智能售货机的“家”,为消费者提供便捷的购买服务。
点位管理主要涉及到三个功能模块,业务流程如下:
- 登录系统:后台管理人员登录后台系统
- 新增区域: 后台管理人员可以添加区域范围,区域范围与运维/运维人员挂钩,区域下可关联点位。
- 新增合作商: 管理人员可以添加合作商,合作商与点位进行关联。
- 新增区域点位: 后台管理人员可以在特定区域内新增点位,这些点位是放置智能售货机的具体位置。
graph TD
A[登录系统]
A --> B[新增区域]
B --> C[新增合作商]
C --> D[新增区域点位]
库表设计
- 参考页面原型和具体需求完成库表设计
区域表: 主键id、区域名称、备注说明
合作商表: 主键id、合作商名称、联系人、联系电话、分成比例、账号、密码
点位表: 主键id、点位名称、详细地址、商圈类型、区域外键、合作商外键
// 你是一位软件工程师,帮我生成MySQL的表结构
需求如下:
1,区域表,表名tb_region,字段有主键id、区域名称
2,合作商表,表名tb_partner,字段有主键id、合作商名称、联系人、联系电话、分成比例(int类型)、账号、密码
3,点位表,表名tb_node,字段有主键id、点位名称、详细地址、商圈类型(int类型)
其他要求:
1,每张表中都有创建时间(create_time)、修改时间(date_time)、创建人(create_by)、修改人(update_by)、备注(remark)这些字段
2,每张表的主键都是自增的
3,区域与点位是一对多的关系,合作商与点位是一对多的关系,请用字段表示出来,并建立外键约束
4,请为所有字段都添加上comment
5,帮我给生成的表中插入一些北京城市相关区域、点位、合作商的测试数据
CREATE TABLE `tb_region` (
`id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY,
`region_name` VARCHAR(255) NOT NULL COMMENT '区域名称',
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`create_by` VARCHAR(64) COMMENT '创建人',
`update_by` VARCHAR(64) COMMENT '修改人',
`remark` TEXT COMMENT '备注'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='区域表';
-- 插入测试数据
INSERT INTO `tb_region` (`region_name`,`remark`) VALUES ('北京市朝阳区','北京市朝阳区'), ('北京市海淀区','北京市海淀区'), ('北京市东城区','北京市东城区');
CREATE TABLE `tb_partner` (
`id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY,
`partner_name` VARCHAR(255) NOT NULL COMMENT '合作商名称',
`contact_person` VARCHAR(64) COMMENT '联系人',
`contact_phone` VARCHAR(15) COMMENT '联系电话',
`profit_ratio` INT COMMENT '分成比例',
`account` VARCHAR(64) COMMENT '账号',
`password` VARCHAR(64) COMMENT '密码',
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`create_by` VARCHAR(64) COMMENT '创建人',
`update_by` VARCHAR(64) COMMENT '修改人',
`remark` TEXT COMMENT '备注'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合作商表';
-- 插入测试数据
INSERT INTO `tb_partner` (`partner_name`, `contact_person`, `contact_phone`, `profit_ratio`, `account`, `password`) VALUES
('合作商A', '张三', '13800138000', 30, 'a001', 'pwdA'),
('合作商B', '李四', '13912345678', 25, 'b002', 'pwdB');
CREATE TABLE `tb_node` (
`id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY,
`node_name` VARCHAR(255) NOT NULL COMMENT '点位名称',
`address` VARCHAR(255) NOT NULL COMMENT '详细地址',
`business_type` INT COMMENT '商圈类型',
`region_id` INT COMMENT '区域ID',
`partner_id` INT COMMENT '合作商ID',
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`create_by` VARCHAR(64) COMMENT '创建人',
`update_by` VARCHAR(64) COMMENT '修改人',
`remark` TEXT COMMENT '备注',
FOREIGN KEY (`region_id`) REFERENCES `tb_region`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`partner_id`) REFERENCES `tb_partner`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='点位表';
-- 插入测试数据
-- 假设区域ID为1对应'北京市朝阳区',合作商ID为1对应'合作商A'
INSERT INTO `tb_node` (`node_name`, `address`, `business_type`, `region_id`, `partner_id`) VALUES
('三里屯点位', '北京市朝阳区三里屯路', 1, 1, 1),
('五道口点位', '北京市海淀区五道口', 2, 2, 2);
对于点位管理数据模型,下面是示意图:
关系字段:region_id、partner_id
数据字典:business_type
生成基础代码
使用若依代码生成器,生成区域管理、合作商管理、点位管理前后端基础代码,导入项目中:
- 创建目录菜单
- 添加数据字典
- 配置代码生成信息
- 下载代码并导入项目
系统管理 → 菜单管理 → 新增菜单
→ 主类目、点位管理、2、node→ 系统管理 → 字典管理
→ 字典名称:商圈类型
字典类型:business_type
→ 在第二页找到商圈类型点进去
→ 新增:旅游区
<u>数据标签:旅游区
数据键值:1
显示排序:1
→ 新增:商场写字楼、2、2;学校33、交通枢纽44→ 系统工具 → 代码生成 → 导入表(tb_node、tb_partner、tb_region)
分别配置表的生成信息
→ 点击区域表 → 字段信息
→ 根据新增区域弹出菜单显示 需要增加区域名称全打勾 备注说明除了查询全打勾 其余全×→ 点击**合作商表** → 生成信息
→ 包路径:com.dkd.manage、生成模块名:manage、生成业务名:region、生成功能名:区域管理、上级菜单:点位管理
→ 代码生成:Partner
→ 基本信息:
实体类名称:Partner
作者:itheima
→ 字段信息:见**帝可得后台管理系统.md**
→ 生成信息:
生成包路径:com.dkd.manage
生成模块名:manage
生成功能名:合作商管理
上级菜单:点位管理→ 点击**点位表**
→ 生成信息:
生成包路径:com.dkd.manage
生成模块名:manage
生成功能名:点位管理
上级菜单:点位管理
→ 字段信息:见**帝可得后台管理系统.md**
→ 基本信息:
实体类名称:Node
表描述:点位表
作者:itheima回到代码生成 选中三张表 生成!!
分别在前后端和数据库 导入java/manage、vue/manage、sql代码
细节:如果当你创建一个模块以后 src.main.java里面没有任何代码 resources里面也没有 它提交仓库的时候是默认空的 所以可以手动添加一个占位符
.gitkeep
虽然什么都不是,但是可以提交空项目模块
区域管理改造
基础页面
需求
- 参考页面原型,完成基础布局展示改造
// 让前端页面自动排序
src\views\manage\region\index.vue
<el-table-column label="序号" type="index" width="50" align="center" prop="id" />
区域管理改造
查看详情,需要显示所有区域下所有点位列表(稍后完成)
在查询区域列表时,同时显示每个区域的点位数,还要新增查看详情
修改后端要参考接口文档,修改前端要参考产品原型
实现此功能方案:
(1) 同步存储在区域表中有点位数的字段,当点位发生变化时候,同步区域表中的点位数(在tb_region里面新增一个
node_count
方案可行考虑缺点:每次点位数据变化时都要更新区域表[增加了工作量],添加数据不一致也会)
(2) 关联查询编写关联查询语句,在mapper层封装
SQL查询:先聚合统计每个区域的点位数,然后与区域表进行关联查询
[提前在idea中下面的通义灵码状态勾选 本地补全模型
、云端模型自动触发
]
数据库返回的数据 要结合前端所需要的返回数据来写
比如接口文档需要返回remark id name nodeCount ==> select r.id,r.region_name,r.remark,ifnull(n.node_count,0) as node_count
-- 传统模式
-- 1.先聚合统计每个区域下的点位数
-- 确定查询表 tb_node
-- 确定分组字段 region_id
select region_id,count(*) as node_count from tb_node group by region_id;
-- 2.然后与区域表进行关联查询 内连接是两个表的交集
select r.id,r.region_name,r.remark,ifnull(n.node_count,0) as node_count
from tb_region r
left join (select region_id,count(*) as node_count
from tb_node group by region_id) n
on r.id=n.region_id;
-- AI辅助编程模式
-- 查询区域表所有的信息,需要显示每个区域的点位数
SELECT r.*, COUNT(n.id) AS node_count FROM tb_region r LEFT JOIN tb_node n ON r.id = n.region_id GROUP BY r.id;
com/dkd/manage/domain/Region.java
package com.dkd.manage.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.dkd.common.annotation.Excel;
import com.dkd.common.core.domain.BaseEntity;
/**
* 区域管理对象 tb_region
*
* @author itheima
* @date 2024-11-12
*/
public class Region extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键id */
private Long id;
/** 区域名称 */
@Excel(name = "区域名称")
private String regionName;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setRegionName(String regionName)
{
this.regionName = regionName;
}
public String getRegionName()
{
return regionName;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("regionName", getRegionName())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.append("createBy", getCreateBy())
.append("updateBy", getUpdateBy())
.append("remark", getRemark())
.toString();
}
}
com/dkd/manage/domain/vo/RegionVo.java
package com.dkd.manage.domain.vo;
import com.dkd.manage.domain.Region;
import lombok.Data;
@Data
public class RegionVo extends Region {
// 点位数量
private Integer nodeCount;
}
com/dkd/manage/mapper/RegionMapper.java
/**
* 查询区域列表
* @param regionVo
* @return
*/
public List<RegionVo> selectRegionVoList(RegionVo regionVo);
mapper/manage/RegionMapper.xml
<select id="selectRegionVoList" resultType="com.dkd.manage.domain.vo.RegionVo">
SELECT r.*, COUNT(n.id) AS node_count FROM tb_region r LEFT JOIN tb_node n ON r.id = n.region_id GROUP BY r.id
</select>
思考:上面的xml中提取的字段是 node_count 而前端让返回的是驼峰式命名 nodeCount, 若依默认关闭了此功能需要手动开启此功能
dkd-parent → resources → mybatis → mybatis-config.xml
<!-- 使用驼峰命名法转换字段 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
Ctrl+F9是热部署 在不新增文件的时候可以直接部署
RegionMapper
/**
* 查询区域管理列表
* @param region
* @return RegionVo集合
*/
public List<RegionVo> selectRegionVoList(Region region);
RegionMapper.xml
<select id="selectRegionVoList" resultType="com.dkd.manage.domain.vo.RegionVo">
select r.id,r.region_name,r.remark,ifnull(n.node_count,0) as node_count from tb_region r
left join (select region_id,count(*) as node_count from tb_node group by region_id) n on r.id=n.region_id
<where>
<if test="regionName != null and regionName != ''"> and r.region_name like concat('%', #{regionName}, '%')</if>
</where>
</select>
IRegionService
/**
* 查询区域管理列表
* @param region
* @return RegionVo集合
*/
public List<RegionVo> selectRegionVoList(Region region);
RegionServiceImpl
/**
* 查询区域管理列表
* @param region
* @return RegionVo集合
*/
@Override
public List<RegionVo> selectRegionVoList(Region region) {
return regionMapper.selectRegionVoList(region);
}
RegionController
/**
* 查询区域管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:region:list')")
@GetMapping("/list")
public TableDataInfo list(Region region)
{
startPage();
List<RegionVo> voList = regionService.selectRegionVoList(region);
return getDataTable(voList);
}
region/index.vue
<!-- 区域列表 -->
<el-table v-loading="loading" :data="regionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="50" align="center" prop="id" />
<el-table-column label="区域名称" align="center" prop="regionName" />
<el-table-column label="点位数" align="center" prop="nodeCount" />
<el-table-column label="备注说明" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:region:edit']">修改</el-button>
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:region:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
小结
- 区域列表改造步骤
- 确定关联查询方案并编写sql
- 创建RegionVo
- 在RegionMapper和xml中添加查询Vo方法和sql
- 在RegionService接口和实现类中添加查询Vo方法
- 修改RegionController查询方法
- 修改前端视图组件
合作商改造-查看详情
密码是明文 改成密文
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码" />
</el-form-item>
隐藏修改时的账号密码id存在时隐藏不存在显示,因为修改和新增共用了一个对话框ui
<el-form-item label="账号" prop="account" v-if="form.id==null">
<el-input v-model="form.account" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码" prop="password" v-if="form.id==null">
<el-input v-model="form.password" type="password" placeholder="请输入密码" />
</el-form-item>
前端需要返回创建时间因为数据返回时有,用v-if判断是否修改显示创建时间
<el-form-item label="创建时间" prop="contactPhone" v-if="form.id!=null">
{{form.createTime}}
</el-form-item>
新增时保存的数据是以明文保存到了数据库此时新增的合作商就是密文了
com/dkd/manage/service/impl/PartnerServiceImpl.java
/**
* 新增合作商管理
*
* @param partner 合作商管理
* @return 结果
*/
@Override
public int insertPartner(Partner partner) {
// 使用SpringSecurity工具类,对前端传入的密码进行加密
partner.setPassword(SecurityUtils.encryptPassword(partner.getPassword()));
partner.setCreateTime(DateUtils.getNowDate());
return partnerMapper.insertPartner(partner);
}
合作商管理改造—合作商详情
- 查看详情,需要显示合作商名称、联系人、联系电话、分成比例
- 在查询合作商列表时,同时显示每个合作商的点位数
- 重置密码,初始密码为123456
/** 借鉴修改流程
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getPartner(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改合作商管理";
});
}
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="getPartnerInfo(scope.row)" v-hasPermi="['manage:partner:query']">查看详情</el-button>
</template>
</el-table-column>
com/dkd/manage/controller/PartnerController.java
/**
* 获取合作商管理详细信息
*/
@PreAuthorize("@ss.hasPermi('manage:partner:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(partnerService.selectPartnerById(id));
}
/** 查看合作商详情 **/
const partnerInfoOpen = ref(false)
function getPartnerInfo(row){
reset();
const _id = row.id
getPartner(_id).then(response => {
form.value = response.data;
partnerInfoOpen.value = true;
});
}
<!-- 查看合作商详情对话框 -->
<el-dialog title="合作商详情" v-model="partnerInfoOpen" width="500px" append-to-body>
<!-- 使用el-descriptions组件以卡片形式展示信息,更加整洁 -->
<el-descriptions :column="2" border>
<el-descriptions-item label="合作商名称">{{ form.partnerName }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ form.contactPerson }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ form.contactPhone }}</el-descriptions-item>
<el-descriptions-item label="分成比例">{{ form.profitRatio }}%</el-descriptions-item>
</el-descriptions>
</el-dialog>
合作商改造—列表查询点位管理→合作商管理→增加点位数
- 实现此功能方案:
后端改造
关联查询编写关联查询语句,在mapper层封装
tb_node(点位表)的partner_id(合作商ID)
关联
tb_partner(合作商表)的id-- 查询合作商表的所有信息,同时显示每个合作商的点位数 select p.*, count(n.id) as node_count from tb_partner p left join tb_node n on p.id = n.partner_id group by p.id
首先先创建一个需要查询新东西的方法增加
com/dkd/manage/domain/vo/PartnerVo.java
package com.dkd.manage.domain.vo;
import com.dkd.manage.domain.Partner;
import lombok.Data;
@Data
public class PartnerVo extends Partner {
// 点位数量
private Integer nodeCount;
}
com/dkd/manage/controller/PartnerController.java
/**
* 查询合作商管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:partner:list')")
@GetMapping("/list")
public TableDataInfo list(Partner partner) {
startPage();
List<PartnerVo> voList = partnerService.selectPartnerVoList(partner);
return getDataTable(voList);
}
com/dkd/manage/service/IPartnerService.java
/**
* 查询合作商列表
* @param partner
* @return
*/
public List<PartnerVo> selectPartnerVoList(Partner partner);
com/dkd/manage/service/impl/PartnerServiceImpl.java
/**
* 查询合作商列表
* @param partner
* @return
*/
@Override
public List<PartnerVo> selectPartnerVoList(Partner partner) {
return partnerMapper.selectPartnerVoList(partner);
}
com/dkd/manage/mapper/PartnerMapper.java
/**
* 查询合作商列表
* @param partner
* @return
*/
public List<PartnerVo> selectPartnerVoList(Partner partner);
mapper/manage/PartnerMapper.xml
</select>
<select id="selectPartnerVoList" resultType="com.dkd.manage.domain.vo.PartnerVo">
select p.*, count(n.id) as node_count
from tb_partner p
left join tb_node n on p.id = n.partner_id
<where>
<if test="partnerName != null and partnerName != ''"> and partner_name like concat('%', #{partnerName}, '%')</if>
</where>
group by p.id
</select>
前端改造
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="50" align="center" prop="id" />
<el-table-column label="合作商名称" align="center" prop="partnerName" />
<el-table-column label="点位数" align="center" prop="nodeCount" />
<el-table-column label="账号" align="center" prop="account" />
<el-table-column label="分成比例" align="center" prop="profitRatio">
<template #default="scope">
{{ scope.row.profitRatio + '%' }}
</template>
</el-table-column>
<el-table-column label="联系人" align="center" prop="contactPerson" />
<el-table-column label="联系电话" align="center" prop="contactPhone" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
合作商改造——重置密码
- 查看详情,需要显示合作商名称、联系人、联系电话、分成比例
- 在查询合作商列表时,同时显示每个合作商的点位数
- 重置密码,初始密码为123456
后端部分
在PartnerController中
/**
* 重置合作商密码
*/
@PreAuthorize("@ss.hasPermi('manage:partner:edit')")
@Log(title = "重置合作商密码", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd/{id}")
public AjaxResult resetpwd(@PathVariable Long id) {//1. 接收参数
//2. 创建合作商对象
Partner partner = new Partner();
partner.setId(id);// 设置id
partner.setPassword(SecurityUtils.encryptPassword("123456"));// 设置加密后的初始密码
//3. 调用service更新密码
return toAjax(partnerService.updatePartner(partner));
}
前端部分
在manage/partner.js
请求api中
// 重置合作商密码
export function resetPartnerPwd(id){
return request({
url: '/manage/partner/resetPwd/' + id,
method: 'put'
})
}
在partner/index.vue
视图组件中参考@click=”handleDelete”,此方法删除时弹出对话框
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300px">
<template #default="scope">
<el-button link type="primary" @click="resetPwd(scope.row)" v-hasPermi="['manage:partner:edit']">重置密码</el-button>
</template>
</el-table-column>
<script>
import { listPartner, getPartner, delPartner, addPartner, updatePartner,resetPartnerPwd } from "@/api/manage/partner";
/* 重置合作商密码 */
function resetPwd(row) {
proxy.$modal.confirm('你确定要重置该合作商密码吗?').then(function () {
return resetPartnerPwd(row.id);
}).then(() => {
proxy.$modal.msgSuccess("重置成功");
}).catch(() => { });
}
</script>
点位管理改造——基础布局
修改前端更简单一些 可以把前端一次性请求1w条数据 后端接口就可以重复调用
在src\views\manage\node\index.vue
视图组件中
import {listRegion} from "@/api/manage/region";
/* 查询所有条件对象 */
const loadAllParams=reactive({
pageNum:1,
pageSize:10000
})
/** 查询区域列表 **/
const regionList=ref([]);
function getRegionList() {
listRegion(loadAllParams).then(response=>{
regionList.value=response.rows;
})
}
getRegionList();
getList();
在src\api\manage\region.js
中
// 查询区域管理列表
export function listRegion(query) {
return request({
url: '/manage/region/list',
method: 'get',
params: query
})
}
新增点位管理的时候想把合作商输入的改成自动获取的下拉框
- 定义js代码向后台发送请求,将请求后的结果封装给合作商parnterList集合
- 将文本框改成下拉框来遍历展示每个合作商的名称,提交时关联合作商的id
<el-table-column label="详细地址" align="center" prop="address" show-overflow-tooltip/> 详细地址多出的部分隐藏只有鼠标移动到才会显示
import {listPartner} from "@/api/manage/partner"
<el-form-item label="合作商ID" prop="partnerId">
<!-- <el-input v-model="form.partnerId" placeholder="请输入合作商ID" /> -->
<el-select v-model="form.partnerId" placeholder="请选择合作商">
<el-option
v-for="item in partnerList"
:key="item.id"
:label="item.partnerName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
/* 查询合作商列表 */
const partnerList=ref([]);
function getPartnerList(){
listPartner(loadAllParams).then(response=>{
partnerList.value = response.rows;
})
}
getPartnerList();
避免每次都要写pageSize:10000 直接搞入js里面
src\api\page.js
/* 查询所有条件对象 */
// const loadAllParams=reactive({
// pageNum:1,
// pageSize:10000
// }) →
export const loadAllParams = reactive({
pageNum: 1,
pageSize: 10000,
});
点位管理改造点位中增加个查看详情(将单表查询改为多表查询咯)
- 查看详情,需要显示当前点位下所有设备列表(稍后完成)
- 在区域详情中,需要显示每个点位的设备数
- 在点位列表查询中,关联显示区域、合作商等信息
- 关联查询:对于设备数量的统计,我们需要执行关联查询,在mapper层封装
- 关联实体:对于区域和合作商的数据,我们会采用Mybatis提供的嵌套查询功能
<resultMap>......</resultMap> #完成手动映射
#解决一对一 或 多对一 映射结果集只有一个对象时完成的ORM的映射封装
<association>......</association> #点位和点位1对1 点位和合作商1对多
#解决一对多场景下来映射多个结果的集合 单个区域表+区域点位列表 映射的是集合!!
<collection>......</collection>
# AI辅助编程模式
-- AI辅助编程模式
-- 你是一个软件开发工程师,现在要根据数据库的sql脚本,查询并显示点位表所有的字段信息,同时显示每个点位的设备数量,sql脚本如下:
create table tb_node
(
id int auto_increment comment '主键id'
primary key,
node_name varchar(255) not null comment '点位名称',
address varchar(255) not null comment '详细地址',
business_type int null comment '商圈类型',
region_id int null comment '区域ID',
partner_id int null comment '合作商ID',
create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间',
update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '修改时间',
create_by varchar(64) null comment '创建人',
update_by varchar(64) null comment '修改人',
remark text null comment '备注',
constraint tb_node_ibfk_1
foreign key (region_id) references tb_region (id)
on update cascade on delete cascade,
constraint tb_node_ibfk_2
foreign key (partner_id) references tb_partner (id)
on update cascade on delete cascade
)
comment '点位表';
create table tb_vending_machine
(
id bigint auto_increment comment '主键'
primary key,
inner_code varchar(15) default '000' null comment '设备编号',
channel_max_capacity int null comment '设备容量',
node_id int not null comment '点位Id',
addr varchar(100) null comment '详细地址',
last_supply_time datetime default '2000-01-01 00:00:00' not null comment '上次补货时间',
business_type int not null comment '商圈类型',
region_id int not null comment '区域Id',
partner_id int not null comment '合作商Id',
vm_type_id int default 0 not null comment '设备型号',
vm_status int default 0 not null comment '设备状态,0:未投放;1-运营;3-撤机',
running_status varchar(100) null comment '运行状态',
longitudes double default 0 null comment '经度',
latitude double default 0 null comment '维度',
client_id varchar(50) null comment '客户端连接Id,做emq认证用',
policy_id bigint null comment '策略id',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
update_time timestamp default CURRENT_TIMESTAMP null comment '修改时间',
constraint vendingmachine_VmId_uindex
unique (inner_code),
constraint tb_vending_machine_ibfk_1
foreign key (vm_type_id) references tb_vm_type (id),
constraint tb_vending_machine_ibfk_2
foreign key (node_id) references tb_node (id),
constraint tb_vending_machine_ibfk_3
foreign key (policy_id) references tb_policy (policy_id)
)
comment '设备表';
-- 查询并显示点位表所有的字段信息,同时显示每个点位的设备数量
SELECT
n.id,
n.node_name,
n.address,
n.business_type,
n.region_id,
n.partner_id,
n.create_time,
n.update_time,
n.create_by,
n.update_by,
n.remark,
COUNT(v.id) AS vm_count
FROM
tb_node n
LEFT JOIN
tb_vending_machine v ON n.id = v.node_id
GROUP BY
n.id;
package com.dkd.manage.domain.vo;
import com.dkd.manage.domain.Node;
import com.dkd.manage.domain.Partner;
import com.dkd.manage.domain.Region;
import lombok.Data;
@Data
public class NodeVo extends Node {
// 设备数量
private Integer vmCount;
// 区域信息
private Region region;
// 合作商信息
private Partner partner;
}
com/dkd/manage/mapper/NodeMapper.java
/**
* 查询点位管理列表
* @param node
* @return
*/
public List<NodeVo> selectNodeVoList(Node node);
<!--
resultType="com.dkd.manage.domin.vo.NodeVo">...
这个是mybatis以前搞的自动映射封装直接把结果映射给了NodeVo的实体类了
嵌套查询就不能使用resultType自动映射 要改为resultMap做自动映射
<resultMap type="NodeVo" id="NodeVoResult">
多表查询一定要起别名噢 不然会报错没有指明where的子句是来自tb_node表的region_id 还是来自tb_vending_machine表的region_id
-->
<select id="selectNodeVoList" parameterType="Node" resultMap="NodeVoResult">
SELECT
n.id,
n.node_name,
n.address,
n.business_type,
n.region_id,
n.partner_id,
n.create_time,
n.update_time,
n.create_by,
n.update_by,
n.remark,
COUNT(v.id) AS vm_count
FROM
tb_node n
LEFT JOIN
tb_vending_machine v ON n.id = v.node_id
<where>
<if test="nodeName != null and nodeName != ''"> and n.node_name like concat('%', #{nodeName}, '%')</if>
<if test="regionId != null "> and n.region_id = #{regionId}</if>
<if test="partnerId != null "> and n.partner_id = #{partnerId}</if>
</where>
GROUP BY
n.id
</select>
多对一标签用association
<resultMap type="NodeVo" id="NodeVoResult">
<result property="id" column="id" />
<result property="nodeName" column="node_name" />
<result property="address" column="address" />
<result property="businessType" column="business_type" />
<result property="regionId" column="region_id" />
<result property="partnerId" column="partner_id" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
<result property="updateBy" column="update_by" />
<result property="remark" column="remark" />
<result property="vmCount" column="vm_count" />
<!--
sql语法拿到region_id去执行区域当中的selectRegionById 方法执行的时候需要传递区域的id
原理:RegionMapper.java中的 public Region selectRegionById(Long id)把条件拿到手并封装给区域的Region对象 返回的Region对象最终映射给NodeVo.java的 private Region region;
怎么执行的映射呢?需要指定java属性名和执行的类型:
property="region"
javaType="Region"
至此完成了mybatis的嵌套查询
-->
<association property="region" javaType="Region" column="region_id" select="com.dkd.manage.mapper.RegionMapper.selectRegionById"/>
<association property="partner" javaType="Partner" column="partner_id" select="com.dkd.manage.mapper.PartnerMapper.selectPartnerById"/>
</resultMap>
<resultMap>......</resultMap> // 完成手动映射 为了实现多表映射情况组合查询
// 解决一对一 或 多对一 映射结果集只有一个对象时完成的ORM的映射封装
<association>......</association> #点位和点位1对1 点位和合作商1对多
// 解决一对多场景下来映射多个结果的集合 单个区域表+区域点位列表 映射的是集合!!
<collection>......</collection>
/*
// <association> 标签允许你在查询结果中嵌套另一个对象。
这样可以方便地在 NodeVo 对象中直接访问 Region 和 Partner 的属性,而不需要额外的查询
property:指定 NodeVo 类中的属性名称,该属性将引用关联的对象。
javaType:指定关联对象的 Java 类型。
column:指定用于关联查询的列名,通常是外键。
select:指定一个子查询的方法,用于根据外键查询关联对象
*/
NodeService
/**
* 查询点位管理列表
* @param node
* @return NodeVo集合
*/
public List<NodeVo> selectNodeVoList(Node node);
NodeServiceImpl
/**
* 查询点位管理列表
*
* @param node
* @return NodeVo集合
*/
@Override
public List<NodeVo> selectNodeVoList(Node node) {
return nodeMapper.selectNodeVoList(node);
}
NodeController
/**
* 查询点位管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:node:list')")
@GetMapping("/list")
public TableDataInfo list(Node node)
{
startPage();
List<NodeVo> voList = nodeService.selectNodeVoList(node);
return getDataTable(voList);
}
com/dkd/manage/mapper/NodeMapper.java
/**
* 查询点位管理列表
* @param node
* @return
*/
public List<NodeVo> selectNodeVoList(Node node);
node/index.vue
<!-- 点位列表 -->
<el-table v-loading="loading" :data="nodeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="50" align="center" prop="id" />
<el-table-column label="点位名称" align="center" prop="nodeName" />
<el-table-column label="所在区域" align="center" prop="region.regionName" />
<el-table-column label="商圈类型" align="center" prop="businessType">
<template #default="scope">
<dict-tag :options="business_type" :value="scope.row.businessType" />
</template>
</el-table-column>
<el-table-column label="合作商" align="center" prop="partner.partnerName" />
<el-table-column label="详细地址" align="center" prop="address" show-overflow-tooltip="true"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:node:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:node:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
区域管理改造-地区详情新增查看详情
src\views\manage\region\index.vue
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="getRegionInfo(scope.row)" v-hasPermi="['manage:region:list']">查看详情</el-button>
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:region:edit']">修改</el-button>
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:region:remove']">删除</el-button>
</template>
</el-table-column>
<!--
template 插槽:
#default="scope":定义默认插槽,scope 是当前行的数据对象。
@click="getRegionInfo(scope.row)":点击按钮时调用 getRegionInfo 函数,并传入当前行的数据。
-->
...
...
/* 查看详情操作按钮 */
function getRegionInfo(row) {
// 查询区域信息
reset();
const _id = row.id
getRegion(_id).then(response => {
form.value = response.data
});
}
<!--
nodeList 变量:
const nodeList = ref([]):定义一个响应式数组 nodeList,用于存储点位列表。
getRegionInfo 函数:
reset():调用 reset 函数,可能用于重置表单或其他状态。
const _id = row.id:获取当前行的 id。
getRegion(_id).then(response => { form.value = response.data }):调用 getRegion 函数查询区域信息,并将返回的数据赋值给 form.value。
loadAllParams.regionId = row.id:设置 loadAllParams 对象的 regionId 属性为当前行的 id。
listNode(loadAllParams).then(response => { nodeList.value = response.rows }):调用 listNode 函数查询点位列表,并将返回的行数据赋值给 nodeList.value。
-->
区域管理里引入点位的api文件
import { listNode } from "@/api/manage/node";
import { loadAllParams } from "@/api/page";
...
/* 查看详情操作按钮 */
const nodeList = ref([]);
function getRegionInfo(row) {
// 查询区域信息
reset();
const _id = row.id
getRegion(_id).then(response => {
form.value = response.data
});
// 查看点位列表
loadAllParams.regionId=row_id
listNode(loadAllParams).then(response => {
nodeList.value = response.rows;
});
}
添加区域管理对话框
<!-- 添加或修改区域管理对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="regionRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="区域名称" prop="regionName">
<el-input v-model="form.regionName" placeholder="请输入区域名称" />
</el-form-item>
<el-form-item label="备注说明" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 查看详情对话框 -->
<el-dialog title="区域详情" v-model="regionInfoOpen" width="500px" append-to-body>
<el-form-item label="区域名称" prop="regionName">
<el-input v-model="form.regionName" disabled />
</el-form-item>
<label>包含点位:</label>
<el-table :data="nodeList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="点位名称" align="center" prop="nodeName" />
<el-table-column label="设备数量" align="center" prop="vmCount" />
</el-table>
</el-dialog>
/* 查看详情操作按钮 */
const nodeList = ref([]);
const regionInfoOpen=ref(false);
function getRegionInfo(row) {
// 查询区域信息
reset();
const _id = row.id
getRegion(_id).then(response => {
form.value = response.data
});
// 查看点位列表
loadAllParams.regionId=row_id
listNode(loadAllParams).then(response => {
nodeList.value = response.rows;
});
regionInfoOpen.value=true;
}
数据完整性
- 在删除区域或合作商数据时,关联的点位数据该如何处理?
tb_region(区域表) tb_node(点位表) tb_partner(合作商表)
id ←region_id:id ← region_id id
partner_id partner_id:id →→→↑
找到设置外键约束(取消约束)dkd → tb_node → Modify Table(old)
找到Foreign Keys
双击打开后将Update rule和Delete rule修改为:no action
**CASCADE(级联操作):**当父表中的某行记录被删除或更新时,与其关联的所有子表中的匹配行也会自动被删除或更新。这种方式适用于希望保持数据一致性的场景,即父记录不存在时,相关的子记录也应该被移除。
**SET NULL(设为空):**若父表中的记录被删除或更新,子表中对应的外键字段会被设置为NULL。选择此选项的前提是子表的外键列允许为NULL值。这适用于那些子记录不再需要明确关联到任何父记录的情况。
**RESTRICT(限制):**在尝试删除或更新父表中的记录之前,数据库首先检查是否有相关联的子记录存在。如果有,则拒绝执行删除或更新操作,以防止意外丢失数据或破坏数据关系的完整性。这是一种保守策略,确保数据间的引用完整性。
**NO ACTION(无操作):**在标准SQL中,NO ACTION是一个关键字,它要求数据库在父表记录被删除或更新前,检查是否会影响子表中的相关记录。在MySQL中,NO ACTION的行为与RESTRICT相同,即如果子表中有匹配的行,则禁止执行父表的删除或更新操作。这意味着如果存在依赖关系,操作将被阻止,从而保护数据的参照完整性。
修改完毕后,如果你尝试进行删除操作,会发现数据库的完整性约束生效了,它会阻止删除操作并给出错误提示。但是,这个错误提示信息可能对于用户来说不够友好,可能会让用户感到困惑。
SQLIntegrityConstraintViolationException
是Java中的一个异常类,这个类通常用于表示SQL数据库操作中的完整性约束违反异常
例如:外键约束、唯一约束等。当数据库操作违反了这些约束时,就会抛出这个异常。
这个错误是由于外键约束导致的。它表明在删除或更新父表的行时,存在外键约束,子表中的相关行会受到影响。
是因为在删除tb_region表中的行时,tb_node表中的region_id外键约束会阻止操作。
如果你在使用Spring框架进行数据库操作,可能会先遇到DataIntegrityViolationException,它是对SQLIntegrityConstraintViolationException的一个更高层次的抽象,旨在提供一种更加面向应用的错误表示。
而SQLIntegrityConstraintViolationException是更底层的异常,直接来源于数据库驱动,包含更多底层数据库相关的细节。
在实际开发中,推荐捕获并处理DataIntegrityViolationException,因为它更符合Spring应用的异常处理模式,同时也可以通过其内部的cause(原因)属性来获取具体的SQLIntegrityConstraintViolationException,进而获取详细的错误信息。
为了提升用户体验,我们可以使用Spring Boot框架的全局异常处理器来捕获这些错误信息,并返回更友好的提示信息给用户。这样,当用户遇到这种情况时,他们将收到一个清晰、易懂的提示,告知他们操作无法完成的原因。
com/dkd/framework/web/exception/GlobalExceptionHandler.java
/**
* 数据完整性异常
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public AjaxResult handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.error(e.getMessage(), e);
if (e.getMessage().contains("foreign")) {
return AjaxResult.error("无法删除该数据,有其他数据引用");
}
return AjaxResult.error("数据完整性异常,请联系管理员");
}
16